#pragma once

#include "Core/Log.h"

namespace Origin
{
    enum class ShaderDataType
    {
        None = 0, Float, Float2, Float3, Float4, Mat3, Mat4, Int, Int2, Int3, Int4, Bool
    };

    static uint32_t ShaderDataTypeSize(ShaderDataType type)
    {
        switch (type)
        {
            case Origin::ShaderDataType::Float:
                return 4;
            case Origin::ShaderDataType::Float2:
                return 8;
            case Origin::ShaderDataType::Float3:
                return 12;
            case Origin::ShaderDataType::Float4:
                return 16;
            case Origin::ShaderDataType::Mat3:
                return 4*3*3;
            case Origin::ShaderDataType::Mat4:
                return 4*4*4;
            case Origin::ShaderDataType::Int:
                return 4;
            case Origin::ShaderDataType::Int2:
                return 8;
            case Origin::ShaderDataType::Int3:
                return 12;
            case Origin::ShaderDataType::Int4:
                return 16;
            case Origin::ShaderDataType::Bool:
                return 1;
        }
        ORIGIN_CORE_ASSERT(false, "Unknown ShaderDataType!");
        return 0;
    }

    struct BufferElement
    {
        std::string name;
        ShaderDataType type;
        uint32_t size;
        uint32_t offset;
        bool normalized;

        // Continue
        BufferElement() {}

        BufferElement(ShaderDataType type, const std::string& name, bool normalized = false) :
            name(name), type(type), size(ShaderDataTypeSize(type)), offset(0), normalized(normalized)
        {
        }

        uint32_t GetComponentCount() const
        {
            switch (type)
            {
                case Origin::ShaderDataType::Float:
                    return 1;
                case Origin::ShaderDataType::Float2:
                    return 2;
                case Origin::ShaderDataType::Float3:
                    return 3;
                case Origin::ShaderDataType::Float4:
                    return 4;
                case Origin::ShaderDataType::Mat3:
                    return 3*3;
                case Origin::ShaderDataType::Mat4:
                    return 4 * 4;
                case Origin::ShaderDataType::Int:
                    return 1;
                case Origin::ShaderDataType::Int2:
                    return 2;
                case Origin::ShaderDataType::Int3:
                    return 3;
                case Origin::ShaderDataType::Int4:
                    return 4;
                case Origin::ShaderDataType::Bool:
                    return 1;
            }

            return 0;
        }

    };

    class BufferLayout
    {
    public:
        BufferLayout() { }
        BufferLayout(const std::initializer_list<BufferElement>& elements): _elements(elements)
        {
            CalculateOffsetAndStride();
        }
        
        inline uint32_t GetStride() const { return _stride; }
        inline const std::vector<BufferElement>& GetElements() const { return _elements; }

        std::vector<BufferElement>::iterator begin() { return _elements.begin(); }
        std::vector<BufferElement>::iterator end() { return _elements.end(); }
        std::vector<BufferElement>::const_iterator begin() const { return _elements.begin(); }
        std::vector<BufferElement>::const_iterator end() const { return _elements.end(); }

    private:
        inline void CalculateOffsetAndStride()
        {
            uint32_t offset = 0;
            for (auto& element: _elements)
            {
                element.offset = offset;
                offset += element.size;
                _stride += element.size;
            }
        }

        std::vector<BufferElement> _elements;
        uint32_t _stride = 0;
    };


    class VertexBuffer
    {
    public:
        virtual ~VertexBuffer() = default;

        virtual void Bind() const = 0;
        virtual void Unbind() const = 0;
        virtual void SetLayout(const BufferLayout& layout) = 0;
        virtual const BufferLayout& GetLayout() const = 0;

        static VertexBuffer* Create(float* vertices, uint32_t size);
    };


    class IndexBuffer
    {
    public:
        virtual ~IndexBuffer() = default;

        virtual void Bind() const = 0;
        virtual void Unbind() const = 0;

        virtual uint32_t GetCount() const = 0;

        static IndexBuffer* Create(uint32_t* indices, uint32_t size);
    };
}

